
 /**
  * LICENCIA LGPL:
  * 
  * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
  * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
  * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
  * la Licencia, o (a su elección) cualquier versión posterior.
  * 
  * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
  * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
  * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
  * License (LGPL) para más detalles
  * 
  * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
  * junto con esta librería; si no es así, escriba a la Free Software Foundation 
  * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
  * <http://www.gnu.org/licenses/>.
  *
  * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
  */
 package es.accv.arangi.base.device.util.pkcs11;
 
 
 import iaik.pkcs.pkcs11.Session;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.security.KeyPair;
 import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
 
 import org.apache.log4j.Logger;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.DERObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.CertificationRequest;
 import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Name;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.jce.PKCS10CertificationRequest;
 
 import es.accv.arangi.base.device.util.CSRUtil;
 import es.accv.arangi.base.util.Util;
 
 
 public class CertificationRequestHelper {
 
 
   /**
    * Loggger de clase
    */
   private static Logger logger = Logger.getLogger(CertificationRequestHelper.class);
   
   
   private static Hashtable            algorithms = new Hashtable();
   private static Hashtable            oids = new Hashtable();
 
   static {
       algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
       algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
       algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
       algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
       algorithms.put("RSAWITHMD5", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
       algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
       algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
       algorithms.put("SHA224WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha224WithRSAEncryption);
       algorithms.put("SHA224WITHRSA", PKCSObjectIdentifiers.sha224WithRSAEncryption);
       algorithms.put("SHA256WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha256WithRSAEncryption);
       algorithms.put("SHA256WITHRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption);
       algorithms.put("SHA384WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha384WithRSAEncryption);
       algorithms.put("SHA384WITHRSA", PKCSObjectIdentifiers.sha384WithRSAEncryption);
       algorithms.put("SHA512WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha512WithRSAEncryption);
       algorithms.put("SHA512WITHRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption);
       algorithms.put("RSAWITHSHA1", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
       algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
       algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
       algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
       algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
       algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
       algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1);
 
       //
       // reverse mappings
       //
       oids.put(new DERObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
       oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
       oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
       oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
       oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");
 
       oids.put(new DERObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
       oids.put(new DERObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
       oids.put(new DERObjectIdentifier("1.2.840.10040.4.3"), "DSAWITHSHA1");
       oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "DSAWITHSHA1");
       
   }
   
 
 
 
  /**
    * Obtiene una peticion de certificado asociada a partir de los parametros.
    * 
    * @param session
    * @param kp
    * @param subjectDN
    * @param subjectAlternativeDN
    * @return PKCS#10
    * @throws Exception 
    */
   public static CertificationRequest getCertificationRequest(Session session, KeyPair kp, String subjectDN, String subjectAlternativeDN) throws Exception {
 
     logger.info("IN: " + Arrays.asList(new Object[] { "<rsaKeys>", subjectDN, subjectAlternativeDN }));
     
     
     if (subjectDN == null) {
       throw new IllegalArgumentException("El campo SubjectDN no puede ser nulo.");
     }
     
     if (kp == null) {
       throw new IllegalArgumentException("El par de claves no puede ser nulo.");
     }
     
     /** TODO: Comprobar:  
      * wscr != null  
      * wscr.getSubjectDN() != null  
      * wscr.getSubjectAlternativeDN() != null */
 
     // -- Obtenemos el nombre estandar
     X509Name x509Name = es.accv.arangi.base.certificate.Certificate.stringToBcX509Name( subjectDN );
     logger.debug("x509Name: " + x509Name);
       
     //-- Parametros para la creacion del bloque de extensiones
     Map extensionsMap = new HashMap();
     extensionsMap.put("subjectAlternativeNameDN", subjectAlternativeDN);
 
 //    extensionsMap.put("ocspUrl",                  "http://ocsp.pki.gva.es");
 //    extensionsMap.put("policyOID",                "1.3.6.1.4.1.8149.3.10.1.0");
 //    extensionsMap.put("cps",                      "http://www.accv.es/legislacion_c.htm");
 //    extensionsMap.put("unotice",                  "Certificado reconocido de CIUDADANO expedido por la Autoridad de Certificación de la Comunitat Valenciana (Pl. Manises 1. CIF S4611001A). CPS y CP en http://www.accv.es");
 //    extensionsMap.put("distributionPoint",        "http://www.accv.es/gestcert/cagva_entidad_der.crl");
     
     //-- Generamos el set de extensiones del certificado
     ASN1Set pkcs10AttributeSet = CSRUtil.getPKCS10Set(kp.getPublic(), kp.getPublic(), extensionsMap);
 
     // Generate PKCS10 certificate request
     byte[]                  bytes = kp.getPublic().getEncoded();
     ByteArrayInputStream    bIn = new ByteArrayInputStream(bytes);
     ASN1InputStream         dIn = new ASN1InputStream(bIn);
 
     CertificationRequestInfo cri = null;
     try {
       
       logger.debug(": \n\n" + ASN1Dump.dumpAsString(pkcs10AttributeSet) + "\n");
       
       SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo((ASN1Sequence)dIn.readObject());
       cri = new CertificationRequestInfo(x509Name, spki, pkcs10AttributeSet);
       
     } catch (IOException e) {
       throw new IllegalArgumentException("Error en la codificacion de la clave publica.");
     }
     logger.debug("CertificationRequestInfo creado.");
     
     
     //-- Creacion del buffer de datos a firmar
     byte[] buffer = cri.getDEREncoded();
     String signatureAlgorithm = "SHA1WithRSA";
 
     DERObjectIdentifier sigOID = (DERObjectIdentifier)algorithms.get(signatureAlgorithm.toUpperCase());
     AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(sigOID);
     
     logger.debug("Array a firmar: " + buffer.length);
     byte[] abFirma = SignUtil.generateSignature(session, kp.getPrivate(), buffer);
     
     logger.debug("firma length: " + abFirma.length );
     
     DERBitString derBitStr = new DERBitString(abFirma);
     
     
     //-- Creamos la certification request
     CertificationRequest req = new CertificationRequest(cri, sigAlgId, derBitStr);
     
 //    ExtendedPKCS10CertificationRequest exp10 = new ExtendedPKCS10CertificationRequest(req.getDEREncoded());
 //    logger.debug("\n\n********************************");
 //    logger.debug("EJBCA pre validation: " + exp10.verify());
 //    
 //    boolean isVal = SignUtil.verifySignature(session, kp.getPublic(), exp10.getCertificationRequestInfo().getEncoded(), exp10.getSignature().getBytes());
 //    logger.debug("isVa: " + isVal);
     
 //    logger.debug("\n\n" + ASN1Dump.dumpAsString(req.getEncoded()) + "\n\n ================================\n");
 //    PKCS10CertificationRequest req = new PKCS10CertificationRequest("SHA1WithRSA", x509Name, rsaKeys.getPublic(), pkcs10AttributeSet, rsaKeys.getPrivate(), "BC");
 
 //    logger.debug("\n\n*** derBitStr == req.getSignature()?? " + ( req.getSignature().equals(derBitStr) ) );
 //    logger.debug("\n\n*** derBitStr: " + new String(Base64.encode(derBitStr.getBytes())) );
 //    logger.debug("\n\n*** req.getSignature(): " + new String(Base64.encode(req.getSignature().getBytes())) );
     
     return req;
 
   }
   
   /**
    * Obtiene una peticion de certificado asociada a partir de los parametros.
    * 
    * @param signatureAlgorithmString
    * @param wscr
    * @param rsaKeys
    * @return PKCS#10
    * @throws Exception 
    */
   public static CertificationRequest getCertificationRequest(String signatureAlgorithmString, KeyPair rsaKeys, String subjectDN, String subjectAlternativeDN) throws Exception {
 
     PrivateKey privateKey = rsaKeys.getPrivate();
     PublicKey publicKey = rsaKeys.getPublic();
     
     /** TODO: Comprobar:  
      * wscr != null  
      * wscr.getSubjectDN() != null  
      * wscr.getSubjectAlternativeDN() != null */
 
     // -- Obtenemos el nombre estandar
     X509Name x509Name = es.accv.arangi.base.certificate.Certificate.stringToBcX509Name( subjectDN );
     logger.debug("x509Name: " + x509Name);
       
     //-- Parametros para la creacion del bloque de extensiones
     Map extensionsMap = new HashMap();
 /*
     extensionsMap.put("keyUsages",                new int[] { X509KeyUsage.digitalSignature, X509KeyUsage.keyEncipherment, X509KeyUsage.dataEncipherment });
     extensionsMap.put("keyPurposeIds",            new KeyPurposeId[] { KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_emailProtection });
 */
     extensionsMap.put("subjectAlternativeNameDN", subjectAlternativeDN);
 /*
     extensionsMap.put("ocspUrl",                  "http://ocsp.pki.gva.es");
     extensionsMap.put("policyOID",                "1.3.6.1.4.1.8149.3.10.1.0");
     extensionsMap.put("cps",                      "http://www.accv.es/legislacion_c.htm");
     extensionsMap.put("unotice",                  "Certificado reconocido de Entidad expedido por la Autoridad de Certificación de la Comunitat Valenciana (Pl. Manises 1. CIF S4611001A). CPS y CP en http://www.accv.es");
     extensionsMap.put("distributionPoint",        "http://www.accv.es/gestcert/cagva_entidad_der.crl");
 */
 
     //-- Generamos el set de extensiones del certificado
     ASN1Set pkcs10AttributeSet = CSRUtil.getPKCS10Set(publicKey, publicKey, extensionsMap);
     
     // Generate PKCS10 certificate request
 //    PKCS10RequestMessage req = new PKCS10RequestMessage(new PKCS10CertificationRequest(signatureAlgorithmString, x509Name, publicKey, pkcs10AttributeSet, privateKey));
     PKCS10CertificationRequest req = new PKCS10CertificationRequest(signatureAlgorithmString, x509Name, publicKey, pkcs10AttributeSet, privateKey);
 
     logger.info("req: " + req);
 
     return req;
 
   }
 
 //  public static boolean verifySignature( Session session, PublicKey key, byte[] buffer, byte[] signature ) {
 //    logger.info("IN");
 ///*
 //    try {
 //      
 //      MessageDigest md = MessageDigest.getInstance("SHA", "BC");
 //      Signature sig = new SignatureEngine("SHA1withRSA", session, Mechanism.RSA_PKCS, md);
 ////      Signature sig = Signature.getInstance("SHA1withRSA", "BC");
 //      
 ////      Signature sig = Signature.getInstance(sigAlgId, "BC");
 //      
 //      sig.initVerify(key);
 //      sig.update(buffer, 0, buffer.length);
 //      
 //      return sig.verify(signature);
 //      
 //    } catch (SignatureException e) {
 //      e.printStackTrace();
 //    } catch (InvalidKeyException e) {
 //      e.printStackTrace();
 //    } catch (NoSuchAlgorithmException e) {
 //      e.printStackTrace();
 //    } catch (NoSuchProviderException e) {
 //      e.printStackTrace();
 //    }
 //*/
 //    // -- second try
 ///*
 //    try {
 //      logger.debug("verifyInit...");
 //      session.verifyInit(Mechanism.RSA_PKCS, (Key) key);
 //      logger.debug("verification initilized.");
 //      
 //      logger.debug("verify...");
 //      session.verify(buffer, signature);
 //      logger.debug("verified.");
 //      
 //    } catch (TokenException e) {
 //      // TODO Auto-generated catch block
 //      e.printStackTrace();
 //      logger.info("Firma No valida");
 //      return false;
 //    }
 //    
 //    logger.info("Firma OK");
 //    return true;
 //*/
 //  //-- tercero
 ///*
 //    Signature sig = null;
 //    try {
 //      sig = Signature.getInstance("SHA1withRSA");
 //    } catch (NoSuchAlgorithmException e) {
 //      // TODO Auto-generated catch block
 //      e.printStackTrace();
 //    }
 //    
 //    logger.debug("Antes de initVerify...");
 //    try {
 //      sig.initVerify(key);
 //    } catch (InvalidKeyException e) {
 //      // TODO Auto-generated catch block
 //      e.printStackTrace();
 //    }
 //    logger.debug("Despues de initVerify.");
 //    
 //    ASN1InputStream is = new ASN1InputStream(new ByteArrayInputStream(buffer));
 //    DERSequence seq = null;
 //    try {
 //      seq = (DERSequence) is.readObject();
 //    } catch (IOException e) {
 //      // TODO Auto-generated catch block
 //      e.printStackTrace();
 //    }
 //    
 //    CertificationRequest cr = new CertificationRequest(seq);
 //    logger.debug("cr: " + cr);
 //    logger.debug("cr Dump: " + ASN1Dump.dumpAsString(cr));
 //
 //    boolean isValid = true;
 //    
 //    
 //    try {
 //      isValid = sig.verify( cr.getSignature().getBytes() );
 //      logger.debug("--- isValid: " + isValid);
 //    } catch (SignatureException e) {
 //      // TODO Auto-generated catch block
 //      e.printStackTrace();
 //      isValid = false;
 //    }
 // */
 //
 //    boolean isValid = false;
 //    try {
 //      
 //      Signature sig = Signature.getInstance("SHA1withRSA", "BC");
 //      
 //      sig.initVerify(key);
 //      sig.update(buffer);
 //      
 //      isValid = sig.verify(signature);
 //      
 //    } catch (SignatureException e) {
 //      e.printStackTrace();
 //    } catch (InvalidKeyException e) {
 //      e.printStackTrace();
 //    } catch (NoSuchAlgorithmException e) {
 //      e.printStackTrace();
 //    } catch (NoSuchProviderException e) {
 //      e.printStackTrace();
 //    }
 //    
 //    logger.debug("isValid: " + isValid);
 //    return isValid;
 //
 //  }
 
   
 
   /**
    * Obtiene la cadena en base64 asociada al P12 del certificado creado.
    * 
    * @param rsaKeys
    * @param cert
    * @param alias
    * @param pwd
    * @return PKCS#12 en base64
    * @throws Exception
    */
   public static String getP12Base64(KeyPair rsaKeys, X509Certificate cert, String alias, String pwd) throws Exception {
 
     if (cert == null) {
       throw new Exception("Recibido certificado nulo.");
     }
 
     // Generamos la cadena de certificados.
     Certificate[] chain = new Certificate[1];
     chain[0] = cert;
 
     // Generamos el KeyStore
     KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
     ks.load(null, pwd.toCharArray());
 
     // Importamos la clave y el certificado.
     ks.setKeyEntry(alias, rsaKeys.getPrivate(), null, chain);
 
     // Creamos el objeto P12
     ByteArrayOutputStream baosPKCS12 = new ByteArrayOutputStream();
     ks.store(baosPKCS12, pwd.toCharArray());
     baosPKCS12.close();
 
     byte[] abPKCS12 = baosPKCS12.toByteArray();
     String pkcs12B64 = new String(Util.encodeBase64(abPKCS12));
 
     // Para evitar el error del InvalidKey, es necesario esto:
     ks.load(new ByteArrayInputStream(abPKCS12), pwd.toCharArray());
 
     return pkcs12B64;
 
   }
   
   
   
   /**
    * Obtiene el identificador del algoritmo a parir del nombre.
    * 
    * @param algorithm
    * @return
    */
   public static AlgorithmIdentifier getAlgorithmId( String algorithm ){
     
     logger.info("IN: " + Arrays.asList(new Object[] { algorithm }));
     
     AlgorithmIdentifier algId = null;
     
     if ( algorithm != null && !"".equals(algorithm) ){
       DERObjectIdentifier oid = (DERObjectIdentifier) algorithms.get(algorithm.toUpperCase());
       algId = new AlgorithmIdentifier(oid);
     }
     
     logger.info("retorna: " + algId);
     
     return algId;
   } 
 
 }